import base64
import binascii
import json
import random
import string
import pyttsx3
import time
import _thread
import mp3play
from urllib import parse
import requests
from Crypto.Cipher import AES

mp3_songs = []
mp3_index = 0

MP3_STATUS_STOPPED = 'stopped'
MP3_STATUS_PLAYING = 'playing'
MP3_STATUS_PAUSED = 'paused'

MP3_CMD_IDLE = 'idle'
MP3_CMD_NEXT = 'next'
MP3_CMD_BACK = 'back'
MP3_CMD_SEARCH = 'search'
MP3_CMD_PAUSE = 'pause'
MP3_CMD_RESUME = 'resume'
MP3_CMD_STOP = 'stop'
MP3_CMD_EXIT = 'exit'

def sapi5Say(wordsStr):
	engine = pyttsx3.init('sapi5')
	# 设置发音速率，默认值为200
	engine.setProperty('rate', 120)

	engine.say(wordsStr)
	engine.runAndWait()

def get_random():
	random_str = ''.join(random.sample(string.ascii_letters + string.digits, 16))
	return random_str

# AES加密要求加密的文本长度必须是16的倍数，密钥的长度固定只能为16,24或32位，因此我们采取统一转换为16位的方法
def len_change(text):
	pad = 16 - len(text) % 16
	text = text + pad * chr(pad)
	text = text.encode("utf-8")
	return text

# AES加密方法
def aes(text, key):
	# 首先对加密的内容进行位数补全，然后使用 CBC 模式进行加密
	iv = b'0102030405060708'
	text = len_change(text)
	cipher = AES.new(key.encode(), AES.MODE_CBC, iv)
	encrypted = cipher.encrypt(text)
	encrypt = base64.b64encode(encrypted).decode()
	return encrypt

# js中的 b 函数，调用两次 AES 加密
# text 为需要加密的文本， str 为生成的16位随机数
def b(text, str):
	first_data = aes(text, '0CoJUm6Qyw8W8jud')
	second_data = aes(first_data, str)
	return second_data

# 这就是那个巨坑的 c 函数
def c(text):
	e = '010001'
	f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
	text = text[::-1]
	result = pow(int(binascii.hexlify(text.encode()), 16), int(e, 16), int(f, 16))
	return format(result, 'x').zfill(131)

# 获取最终的参数 params 和 encSecKey 的方法
def get_final_param(text, str):
	params = b(text, str)
	encSecKey = c(str)
	return {'params': params, 'encSecKey': encSecKey}

# 通过参数获取搜索歌曲的列表
def get_music_list(params, encSecKey):
	url = "https://music.163.com/weapi/cloudsearch/get/web?csrf_token="

	payload = 'params=' + parse.quote(params) + '&encSecKey=' + parse.quote(encSecKey)
	headers = {
		'authority': 'music.163.com',
		'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36',
		'content-type': 'application/x-www-form-urlencoded',
		'accept': '*/*',
		'origin': 'https://music.163.com',
		'sec-fetch-site': 'same-origin',
		'sec-fetch-mode': 'cors',
		'sec-fetch-dest': 'empty',
		'referer': 'https://music.163.com/search/',
		'accept-language': 'zh-CN,zh;q=0.9',
	}
	response = requests.request("POST", url, headers=headers, data=payload)
	return response.text

# 获取歌曲的播放链接
def get_reply(params, encSecKey):
	url = "https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token="
	payload = 'params=' + parse.quote(params) + '&encSecKey=' + parse.quote(encSecKey)
	headers = {
		'authority': 'music.163.com',
		'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36',
		'content-type': 'application/x-www-form-urlencoded',
		'accept': '*/*',
		'origin': 'https://music.163.com',
		'sec-fetch-site': 'same-origin',
		'sec-fetch-mode': 'cors',
		'sec-fetch-dest': 'empty',
		'referer': 'https://music.163.com/',
		'accept-language': 'zh-CN,zh;q=0.9'
	}
	response = requests.request("POST", url, headers=headers, data=payload)
	return response.text

def getMp3Status():
	status_flag = ''
	while True:
		with open(r"c:\AICHAIN\myAI\test\mp3status.txt", 'r', encoding='utf-8') as f:
			status_flag = f.readline()
			f.close()
			break
		time.sleep(1)
		
	return status_flag

def setMp3Status(status_flag):
	while True:
		with open(r"c:\AICHAIN\myAI\test\mp3status.txt", 'w', encoding='utf-8') as f:
			f.write(status_flag)
			f.close()
			break
		time.sleep(1)

def resetMp3CMD():
	while True:
		with open(r"c:\AICHAIN\myAI\test\mp3ctrl.txt", 'w', encoding='utf-8') as f:
			f.write(MP3_CMD_IDLE)
			f.close()
			break
		time.sleep(1)

def getMp3CMD():
	cmdflag = ''
	while True:
		with open(r"c:\AICHAIN\myAI\test\mp3ctrl.txt", 'r', encoding='utf-8') as f:
			cmdflag = f.readline()
			f.close()
			break
		time.sleep(1)
		
	return cmdflag

def setMp3CMD(mode):
	while True:
		# only can set mp3 cmd when cmd flag is idle
		if getMp3CMD() != MP3_CMD_IDLE :
			time.sleep(1)
			continue

		with open(r"c:\AICHAIN\myAI\test\mp3ctrl.txt", 'w', encoding='utf-8') as f:
			f.write(mode)
			f.close()
			break
		time.sleep(1)

def setMp3SearchKey(keystr):
	while True:
		with open(r"c:\AICHAIN\myAI\test\mp3.txt", 'w', encoding='utf-8') as f:
			f.write(keystr)
			f.close()
			break
		time.sleep(1)

def getMp3SearchKey():
	keystr = 'hello'
	while True:
		with open(r"c:\AICHAIN\myAI\test\mp3.txt", 'r', encoding='utf-8') as f:
			keystr = f.readline()
			f.close()
			break
		time.sleep(1)
		
	return keystr

# 方法入口
if __name__=='__main__':
	resetMp3CMD()
	setMp3Status(MP3_STATUS_STOPPED)

	while True:
		mp3_cmd = getMp3CMD()
		if mp3_cmd == MP3_CMD_IDLE :
			time.sleep(1)
			continue

		print(mp3_cmd)

		if mp3_cmd == MP3_CMD_EXIT :
			break

		elif mp3_cmd == MP3_CMD_STOP :
			# stop mp3 play , 虽然clip是在下面才首次定义并赋值的，但是python里面把这些同名的，都认为是这个函数范围内的变量！！！
			if getMp3Status() != MP3_STATUS_STOPPED :
				clip.stop()
			setMp3Status(MP3_STATUS_STOPPED)

		elif mp3_cmd == MP3_CMD_NEXT :
			# 播放下一首
			if getMp3Status() != MP3_STATUS_STOPPED :
				clip.stop()

			if mp3_index+1 >= len(mp3_songs) :
				# the end
				sapi5Say('这是最后一首，后面没有歌曲了')
				setMp3Status(MP3_STATUS_STOPPED)
			else :
				mp3_index = mp3_index + 1
				clip = mp3play.load(mp3_songs[mp3_index])
				clip.play()
				setMp3Status(MP3_STATUS_PLAYING)

		elif mp3_cmd == MP3_CMD_BACK :
			# 播放上一首
			if getMp3Status() != MP3_STATUS_STOPPED :
				clip.stop()

			if mp3_index > 0 and len(mp3_songs) > 1 :
				# the end
				sapi5Say('这是第一首，前面没有歌曲了')
				setMp3Status(MP3_STATUS_STOPPED)
			else :
				mp3_index = mp3_index - 1
				clip = mp3play.load(mp3_songs[mp3_index])
				clip.play()
				setMp3Status(MP3_STATUS_PLAYING)
		
		elif mp3_cmd == MP3_CMD_PAUSE :
			if mp3_index >= 0 and len(mp3_songs) > 0 and mp3_index < len(mp3_songs) and getMp3Status() == MP3_STATUS_PLAYING :
				clip.pause()
				setMp3Status(MP3_STATUS_PAUSED)

		elif mp3_cmd == MP3_CMD_RESUME :
			if mp3_index >= 0 and len(mp3_songs) > 0 and mp3_index < len(mp3_songs) and getMp3Status() == MP3_STATUS_PAUSED :
				clip.unpause()
				setMp3Status(MP3_STATUS_PLAYING)

		elif mp3_cmd == MP3_CMD_SEARCH :
			mp3_songs.clear()
			song_name = getMp3SearchKey()
			d = {"hlpretag": "<span class=\"s-fc7\">", "hlposttag": "</span>", "s": song_name, "type": "1", "offset": "0","total": "true", "limit": "30", "csrf_token": ""}
			d = json.dumps(d)
			random_param = get_random()
			param = get_final_param(d, random_param)
			song_list = get_music_list(param['params'], param['encSecKey'])
			if len(song_list) > 0:
				song_list = json.loads(song_list)['result']['songs']
				for i, item in enumerate(song_list):
					item = json.dumps(item)
					print(str(i) + "：" + json.loads(str(item))['name'])
					d = {"ids": "[" + str(json.loads(str(item))['id']) + "]", "level": "standard", "encodeType": "","csrf_token": ""}
					d = json.dumps(d)
					param = get_final_param(d, random_param)
					song_info = get_reply(param['params'], param['encSecKey'])
					if len(song_info) > 0:
						song_info = json.loads(song_info)
						song_url = json.dumps(song_info['data'][0]['url'], ensure_ascii=False)

						mp3_songs.append(song_url.strip("\""))
						print(song_url)
					else:
						print("该首歌曲解析失败，可能是因为歌曲格式问题")
				wordsStr = "找到了"+ str(len(mp3_songs)) +" 首歌曲，开始欣赏吧"
				sapi5Say(wordsStr)

				if len(mp3_songs) > 0 :
					mp3_index = 0
					# start play mp3: mp3_songs[mp3_index]
					clip = mp3play.load(mp3_songs[mp3_index])
					clip.play()

					print("mp3 is playing...")
					setMp3Status(MP3_STATUS_PLAYING)
				else :
					setMp3Status(MP3_STATUS_STOPPED)
			else:
				print("很抱歉，未能搜索到相关歌曲信息")
				wordsStr = "我的乖乖，没找到相关音乐"
				sapi5Say(wordsStr)

				setMp3Status(MP3_STATUS_STOPPED)
		else :
			time.sleep(1)
		
		# if cmd is not idle, then need reset cmd to idle, then process according to cmd
		resetMp3CMD()

